;-------------------------------------
;sys_boot.asm
;参照《自己动手写操作系统》chapter4的boot.asm
;任务为从磁盘将loader.bin写入内存，转交控制权
;-------------------------------------

org 07c00h

;定义实模式下的栈地址，Loader将要被加载到的地址
BaseOfStack equ 07c00h
BaseOfLoader equ 09000h
OffsetOfLoader equ 0100h

;下面是有关待读磁盘的一些属性
;根目录占用扇区数，由根目录最大文件数*文件描述符的长度(0x20)再除以每个扇区的字节数向上取整
RootDirSectors equ 14;
;根目录的第一个扇区号，是引导扇区加上两个FAT(9扇区)
FstRootDirSector equ 19;

;定义部分结束，下面为正式的引导磁盘的内容

jmp short LABEL_BEGIN
nop
BS_OEMName	DB 'ForrestY'	; OEM String, 必须 8 个字节
BPB_BytsPerSec	DW 512		; 每扇区字节数
BPB_SecPerClus	DB 1		; 每簇多少扇区
BPB_RsvdSecCnt	DW 1		; Boot 记录占用多少扇区
BPB_NumFATs	DB 2		; 共有多少 FAT 表
BPB_RootEntCnt	DW 224		; 根目录文件数最大值
BPB_TotSec16	DW 2880		; 逻辑扇区总数
BPB_Media	DB 0xF0		; 媒体描述符
BPB_FATSz16	DW 9		; 每FAT扇区数
BPB_SecPerTrk	DW 18		; 每磁道扇区数
BPB_NumHeads	DW 2		; 磁头数(面数)
BPB_HiddSec	DD 0		; 隐藏扇区数
BPB_TotSec32	DD 0		; 如果 wTotalSectorCount 是 0 由这个值记录扇区数
BS_DrvNum	DB 0		; 中断 13 的驱动器号
BS_Reserved1	DB 0		; 未使用
BS_BootSig	DB 29h		; 扩展引导标记 (29h)
BS_VolID	DD 0		; 卷序列号
BS_VolLab	DB 'OrangeS0.02'; 卷标, 必须 11 个字节
BS_FileSysType	DB 'FAT12   '	; 文件系统类型, 必须 8个字节

LABEL_BEGIN:
	;jmp $
	mov ax,cs
	mov ds,ax
	mov es,ax
	mov ss,ax
	mov sp,BaseOfStack

	;对显示屏清屏，由于实模式下没有显存段，用int 10h中断清屏
	mov ax,0600h
	mov bx,0700h
	mov cx,0
	mov dx,0184fh
	int 10h

;显示"Booting"
	;提供字符串地址
	mov ax,ds
	mov es,ax
	mov ax,BootingStr
	mov bp,ax

	;提供写字的位置，字的颜色，字串长度
	mov ax,01301h
	mov bx,000ch
	mov cx,08h
	mov dx,0
	int 10h

	mov ax,cs
	mov es,ax

	;软驱复位
	xor ah,ah
	xor dl,dl
	int 13h

LABEL_FIND_LOADER:
cmp word [RootSectors],RootDirSectors
jz LABEL_NO_LOADER
	mov ax,BaseOfLoader
	mov es,ax
	mov bx,OffsetOfLoader
	mov cl,1
	mov ax,FstRootDirSector
	add ax,word [RootSectors]
	inc word [RootSectors]
	call ReadSector

	mov si,FileName
	mov di,OffsetOfLoader
	cld
	;每个扇区由16条长为20的记录
	mov dx,10h
	LABEL_READ_ENTRIES:
	cmp dx,0
	;16条记录读完没找到
	jz LABEL_NEXT_SECTOR
		dec dx
		mov cx,11
		LABEL_CMP_STR:
		cmp cx,0
		jz LABEL_LOADER_FOUND
			dec cx
			lodsb
			cmp al,byte [es:di]
			jnz LABEL_DIFF_CHAR
			inc di
			jmp LABEL_CMP_STR

		LABEL_DIFF_CHAR:
		and di,0ffe0h
		add di,20h
		mov si,FileName
		jmp LABEL_READ_ENTRIES

	LABEL_NEXT_SECTOR:
	jmp LABEL_FIND_LOADER

LABEL_NO_LOADER:
	mov ax,ds
	mov es,ax
	mov ax,NotFoundMsg
	mov bp,ax

	;提供写字的位置，字的颜色，字串长度
	mov ax,01401h
	mov bx,000ch
	mov cx,0Eh
	mov dx,0
	int 10h

;如果找到的话，dx为对应的条目，di为字符串尾偏移
LABEL_LOADER_FOUND:
mov ax,FstRootDirSector
add ax,RootDirSectors
and di,0ffe0h
add di,01ah
mov cx,word [es:di]
push cx
add cx,ax
sub cx,02h
mov ax,BaseOfLoader
mov es,ax
mov bx,OffsetOfLoader
mov ax,cx

LABEL_LOAD_FILE:
	push	ax
	push	bx
	mov	ah, 0Eh
	mov	al, '.'	
	mov	bl, 0Fh
	int	10h
	pop	bx
	pop	ax

	mov cl,1
	call ReadSector
	pop ax
	call GetFATEntry
	cmp ax, 0fffh
	jz LABEL_FILE_LOADED
	push ax
	mov dx,14
	add ax,dx
	add ax,FstRootDirSector
	sub ax,02h
	jmp LABEL_LOAD_FILE

LABEL_FILE_LOADED:
	mov ax,ds
	mov es,ax
	mov ax,LoadedMsg
	mov bp,ax

	;提供写字的位置，字的颜色，字串长度
	mov ax,01301h
	mov bx,000ch
	mov cx,013h
	mov dx,0
	int 10h
	jmp BaseOfLoader:OffsetOfLoader

bOdd			db	0		; 奇数还是偶数

ReadSector:
	; -----------------------------------------------------------------------
	; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号)
	; -----------------------------------------------------------------------
	; 设扇区号为 x
	;                           ┌ 柱面号 = y >> 1
	;       x           ┌ 商 y ┤
	; -------------- => ┤      └ 磁头号 = y & 1
	;  每磁道扇区数     │
	;                   └ 余 z => 起始扇区号 = z + 1
	push	bp
	mov	bp, sp
	sub	esp, 2			; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2]

	mov	byte [bp-2], cl
	push	bx			; 保存 bx
	mov	bl, [BPB_SecPerTrk]	; bl: 除数
	div	bl			; y 在 al 中, z 在 ah 中
	inc	ah			; z ++
	mov	cl, ah			; cl <- 起始扇区号
	mov	dh, al			; dh <- y
	shr	al, 1			; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)
	mov	ch, al			; ch <- 柱面号
	and	dh, 1			; dh & 1 = 磁头号
	pop	bx			; 恢复 bx
	; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^
	mov	dl, [BS_DrvNum]		; 驱动器号 (0 表示 A 盘)
.GoOnReading:
	mov	ah, 2			; 读
	mov	al, byte [bp-2]		; 读 al 个扇区
	int	13h
	jc	.GoOnReading		; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止

	add	esp, 2
	pop	bp

	ret

GetFATEntry:
	push	es
	push	bx
	push	ax
	mov	ax, BaseOfLoader; `.
	sub	ax, 0100h	;  | 在 BaseOfLoader 后面留出 4K 空间用于存放 FAT
	mov	es, ax		; /
	pop	ax
	mov	byte [bOdd], 0
	mov	bx, 3
	mul	bx			; dx:ax = ax * 3
	mov	bx, 2
	div	bx			; dx:ax / 2  ==>  ax <- 商, dx <- 余数
	cmp	dx, 0
	jz	LABEL_EVEN
	mov	byte [bOdd], 1
LABEL_EVEN:;偶数
	; 现在 ax 中是 FATEntry 在 FAT 中的偏移量,下面来
	; 计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区)
	xor	dx, dx			
	mov	bx, [BPB_BytsPerSec]
	div	bx ; dx:ax / BPB_BytsPerSec
		   ;  ax <- 商 (FATEntry 所在的扇区相对于 FAT 的扇区号)
		   ;  dx <- 余数 (FATEntry 在扇区内的偏移)。
	push	dx
	mov	bx, 0 ; bx <- 0 于是, es:bx = (BaseOfLoader - 100):00
	add	ax, 1 ; 此句之后的 ax 就是 FATEntry 所在的扇区号
	mov	cl, 2
	call	ReadSector ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界
			   ; 发生错误, 因为一个 FATEntry 可能跨越两个扇区
	pop	dx
	add	bx, dx
	mov	ax, [es:bx]
	cmp	byte [bOdd], 1
	jnz	LABEL_EVEN_2
	shr	ax, 4
LABEL_EVEN_2:
	and	ax, 0FFFh

LABEL_GET_FAT_ENRY_OK:

	pop	bx
	pop	es
	ret

LABEL_DATA:
	BootingStr db "Booting",0
	FileName db "LOADER  BIN",0
	LoadedMsg db "Load successfully!",0
	NotFoundMsg db "No Loader.bin",0
	RootSectors dw 0

times 	510-($-$$)	db	0	; 填充剩下的空间，使生成的二进制代码恰好为512字节
dw 	0xaa55				; 结束标志